"""
统一AI服务请求处理器
提供统一的HTTP请求处理、错误处理和响应解析功能
避免在多个AI服务中重复编写相似代码
"""

import httpx
import base64
import logging
from typing import Dict, Any, Optional, Union
from dataclasses import dataclass
from abc import ABC, abstractmethod

logger = logging.getLogger(__name__)

@dataclass
class RequestConfig:
    """统一的请求配置"""
    url: str
    headers: Dict[str, str]
    timeout: int = 30
    max_retries: int = 3

@dataclass
class ResponseConfig:
    """统一的响应配置"""
    content_path: str  # 内容字段路径，如 "choices[0].message.content" 或 "response"
    expected_status: int = 200
    error_prefix: str = "API调用失败"

class RequestDataBuilder(ABC):
    """请求数据构建器抽象类"""

    @abstractmethod
    def build_request_data(self, base_config: Dict[str, Any], image_base64: str) -> Dict[str, Any]:
        """
        构建特定服务的请求数据
        :param base_config: 基础配置
        :param image_base64: base64编码的图片
        :return: 完整的请求数据
        """
        pass

class UnifiedRequestProcessor:
    """统一的AI服务请求处理器"""

    def __init__(self, service_name: str, data_builder: RequestDataBuilder):
        self.service_name = service_name
        self.data_builder = data_builder
        self.logger = logging.getLogger(f"{__name__}.{service_name}")

    async def process_request(
        self,
        request_config: RequestConfig,
        response_config: ResponseConfig,
        image_bytes: bytes,
        base_request_config: Optional[Dict[str, Any]] = None
    ) -> str:
        """
        统一的请求处理流程

        :param request_config: 请求配置
        :param response_config: 响应配置
        :param image_bytes: 图片字节数据
        :param base_request_config: 基础请求配置（可选）
        :return: 分析结果文本
        """
        try:
            # 编码图片
            image_base64 = base64.b64encode(image_bytes).decode('utf-8')

            # 使用数据构建器构建请求数据
            if base_request_config is None:
                base_request_config = {}
            request_data = self.data_builder.build_request_data(base_request_config, image_base64)

            self.logger.info(f"[{self.service_name}] 发送请求到: {request_config.url}")

            # 发送请求（带重试机制）
            for attempt in range(request_config.max_retries):
                try:
                    async with httpx.AsyncClient(timeout=request_config.timeout) as client:
                        response = await client.post(
                            request_config.url,
                            headers=request_config.headers,
                            json=request_data
                        )

                    self.logger.info(f"[{self.service_name}] 响应状态码: {response.status_code}")

                    # 检查响应状态
                    if response.status_code != response_config.expected_status:
                        error_text = response.text
                        self.logger.error(
                            f"[{self.service_name}] {response_config.error_prefix} - "
                            f"状态码: {response.status_code}, 错误信息: {error_text}"
                        )
                        if attempt == request_config.max_retries - 1:
                            raise Exception(f"{self.service_name} API error: {response.status_code}, {error_text}")
                        continue

                    # 解析响应
                    result = response.json()
                    self.logger.info(f"[{self.service_name}] 响应接收成功")

                    # 提取内容
                    content = self._extract_content(result, response_config.content_path)
                    self.logger.info(f"[{self.service_name}] 分析结果: {content[:100]}...")

                    return content

                except httpx.RequestError as e:
                    self.logger.warning(f"[{self.service_name}] 请求失败 (尝试 {attempt+1}/{request_config.max_retries}): {e}")
                    if attempt == request_config.max_retries - 1:
                        raise Exception(f"{self.service_name} request failed after {request_config.max_retries} attempts: {e}")
                except Exception as e:
                    self.logger.error(f"[{self.service_name}] {response_config.error_prefix}: {e}")
                    if attempt == request_config.max_retries - 1:
                        raise
                    continue

            # 这行代码理论上不会执行到，但为了完整性保留
            raise Exception(f"{self.service_name} failed after all retry attempts")

        except Exception as e:
            self.logger.error(f"[{self.service_name}] {response_config.error_prefix}: {e}")
            raise

    def _extract_content(self, response: Dict[str, Any], content_path: str) -> str:
        """
        从响应中提取内容，支持复杂的路径表达式

        :param response: API响应字典
        :param content_path: 内容路径，如 "choices[0].message.content" 或 "response"
        :return: 提取的内容字符串
        """
        try:
            # 处理数组索引和点分隔路径
            parts = content_path.replace('[', '.').replace(']', '').split('.')
            current: Union[Dict, list] = response

            for part in parts:
                if not part:  # 跳过空字符串
                    continue

                if part.isdigit():
                    # 数组索引
                    if isinstance(current, list):
                        current = current[int(part)]
                    else:
                        raise ValueError(f"Expected list at path part '{part}', got {type(current)}")
                else:
                    # 对象属性
                    if isinstance(current, dict):
                        current = current[part]
                    else:
                        raise ValueError(f"Expected dict at path part '{part}', got {type(current)}")

            return str(current)

        except (KeyError, IndexError, ValueError, TypeError) as e:
            self.logger.error(
                f"[{self.service_name}] 解析响应失败: {e}, "
                f"路径: {content_path}, 响应: {response}"
            )
            raise Exception(f"Failed to extract content from response using path '{content_path}': {e}")

# 预定义的数据构建器实现

class OpenAICompatibleDataBuilder(RequestDataBuilder):
    """OpenAI兼容格式的数据构建器（适用于Doubao和LMStudio）"""

    def build_request_data(self, base_config: Dict[str, Any], image_base64: str) -> Dict[str, Any]:
        """构建OpenAI兼容格式的请求数据"""
        messages = base_config.get('messages', [])
        if not messages:
            # 默认消息格式
            messages = [{
                "role": "user",
                "content": [
                    {
                        "type": "text",
                        "text": base_config.get('prompt', '请描述图片内容')
                    },
                    {
                        "type": "image_url",
                        "image_url": {"url": f"data:image/jpeg;base64,{image_base64}"}
                    }
                ]
            }]

        return {
            "model": base_config.get('model', 'gpt-4-vision-preview'),
            "messages": messages,
            "max_tokens": base_config.get('max_tokens', 1000),
            "temperature": base_config.get('temperature', 0.7)
        }

class OllamaDataBuilder(RequestDataBuilder):
    """Ollama格式的数据构建器"""

    def build_request_data(self, base_config: Dict[str, Any], image_base64: str) -> Dict[str, Any]:
        """构建Ollama格式的请求数据"""
        return {
            "model": base_config.get('model', 'llama3'),
            "prompt": base_config.get('prompt', '请描述图片内容'),
            "images": [image_base64],
            "stream": base_config.get('stream', False),
            "options": {
                "temperature": base_config.get('temperature', 0.7),
                "num_predict": base_config.get('num_predict', 1000)
            }
        }
